package behaviorTree

import (
	"fmt"
	"gitee.com/kingzyt/common/log"
	. "gitee.com/kingzyt/common/rouletteSelection"
)

type Selector struct {
	*BaseNode
}

func NewSelector(name string) *Selector {
	rlt := &Selector{
		BaseNode: NewBaseNode(name, NodeType_Selector),
	}

	rlt.CheckValidationFunc = rlt.checkChildCntOver0

	return rlt
}

func (self *Selector) Enter(data IRuntimeBTData, stateIn NodeState, localDataIn interface{}) (childNode INode, state NodeState, localDataOut interface{}) {
	switch stateIn {
	case BEGIN:
		return self.children[0], RUNNING, int32(0)

	case SUCCESS:
		return nil, SUCCESS, nil

	case FAILURE:
		curIdx := localDataIn.(int32)
		curIdx++
		if curIdx > 0 && curIdx < int32(len(self.children)) {
			return self.children[curIdx], RUNNING, curIdx
		} else {
			return nil, FAILURE, nil
		}
	}
	return
}

/////////////////////////////////////////////////////////////////////////
type IfElse struct {
	*BaseNode
}

func NewIfElse(name string) *IfElse {
	rlt := &IfElse{
		BaseNode: NewBaseNode(name, NodeType_IfElse),
	}

	rlt.CheckValidationFunc = rlt.checkChild

	return rlt
}

// Yes or No are all OK by default
func (self *IfElse) AddChildYN(YChild INode, NChild INode) {
	if YChild == nil {
		YChild = NewSuccess(self.name + "_Y")
	}
	if NChild == nil {
		NChild = NewSuccess(self.name + "_N")
	}
	self.AddChild(YChild) // 0 is Y
	self.AddChild(NChild) // 1 is N
}

func (self *IfElse) checkChild() (rlt bool, errInfo string) {
	rlt, errInfo = self.checkChildCnt(2)
	return
}

func (self *IfElse) Enter(data IRuntimeBTData, stateIn NodeState, localDataIn interface{}) (childNode INode, state NodeState, localDataOut interface{}) {
	switch stateIn {
	case BEGIN:
		var idx int32
		obj := data.GetData(self.GetName())
		if obj == nil {
			panic("ifelse(" + self.GetName() + ") proc[ func() bool ] is nil")
		}
		judgeFunc := obj.(func() bool)
		if judgeFunc() {
			log.Info(self.name, log.Trace, "Y")
			idx = 0
		} else {
			log.Info(self.name, log.Trace, "N")
			idx = 1
		}

		return self.children[idx], RUNNING, nil

	case SUCCESS, FAILURE:
		return nil, stateIn, nil
	}
	return
}

/////////////////////////////////////////////////////////////////////
type SelectorRandWSelectElem struct {
	child  INode
	weight int32
	tick   float32
}

func (self *SelectorRandWSelectElem) GetRate() float32 {
	return float32(self.weight)
}
func (self *SelectorRandWSelectElem) GetTick() float32 {
	return self.tick
}
func (self *SelectorRandWSelectElem) SetTick(tick float32) {
	self.tick = tick
}
func (self *SelectorRandWSelectElem) String() string {
	return fmt.Sprintf("child:%s, r:%d, t:%.0f", self.child.GetName(), self.weight, self.tick)
}

type SelectorRandW struct {
	*BaseNode

	weights           []SelectElem
	rouletteSelection *RouletteSelection
}

func NewSelectorRandW(name string) *SelectorRandW {
	rlt := &SelectorRandW{
		BaseNode: NewBaseNode(name, NodeType_SelectorRandW),
	}

	rlt.CheckValidationFunc = rlt.checkChild

	return rlt
}

func (self *SelectorRandW) AddWChild(weight int32, child INode) {
	if weight <= 0 || child == nil {
		return
	}

	self.weights = append(self.weights, &SelectorRandWSelectElem{child: child, weight: weight})

	self.AddChild(child)
}

func (self *SelectorRandW) checkChild() (rlt bool, errInfo string) {
	ok := false
	for _, elem := range self.weights {
		if elem.(*SelectorRandWSelectElem).weight > 0 {
			ok = true
			break
		}
	}

	if ok {
		var err error
		self.rouletteSelection, err = NewRouletteSelection(self.GetName(), &self.weights)
		if err != nil {
			return false, err.Error()
		}
		return true, ""
	} else {
		return false, "all child weight is 0, at least one child weight > 0"
	}
}

func (self *SelectorRandW) Enter(data IRuntimeBTData, stateIn NodeState, localDataIn interface{}) (childNode INode, state NodeState, localDataOut interface{}) {
	switch stateIn {
	case BEGIN:
		target := self.rouletteSelection.Rand().(*SelectorRandWSelectElem)
		if target == nil || target.child == nil {
			// no one be selected, left all weights are 0
			return nil, FAILURE, nil
		}

		//log.Info(self.name, log.Trace, "go to branch idx:%d", self.curIdx)
		return target.child, RUNNING, nil

	case SUCCESS, FAILURE:
		return nil, stateIn, nil
	}
	return
}

///////////////////////////////////////////////////////
type StateSelector struct {
	*BaseNode
	stateChildren map[int]INode
}

func NewStateSelector(name string) *StateSelector {
	rlt := &StateSelector{
		BaseNode:      NewBaseNode(name, NodeType_StateSelector),
		stateChildren: map[int]INode{},
	}

	rlt.CheckValidationFunc = rlt.checkChild

	return rlt
}
func (self *StateSelector) checkChild() (rlt bool, errInfo string) {
	rlt = len(self.stateChildren) >= 1
	if !rlt {
		errInfo = "child count should be >= 1"
	}
	return
}
func (self *StateSelector) AddStateChild(state int, child INode) {
	_, existed := self.stateChildren[state]
	if existed {
		panic(fmt.Sprintf("state:%d has been set child", state))
	}
	self.stateChildren[state] = child
	self.AddChild(child)
}
func (self *StateSelector) Enter(data IRuntimeBTData, stateIn NodeState, localDataIn interface{}) (childNode INode, state NodeState, localDataOut interface{}) {
	switch stateIn {
	case BEGIN:
		obj := data.GetData(self.GetName())
		if obj == nil {
			panic("stateSel(" + self.GetName() + ") proc[ func() int ] is nil")
		}

		state := obj.(func() int)()
		child, ok := self.stateChildren[state]
		if !ok || child == nil {
			panic(fmt.Sprintf("stateSel(%s) state(%d) is no mapping child", self.GetName(), state))
		}
		return child, RUNNING, nil

	case SUCCESS, FAILURE:
		return nil, stateIn, nil
	}
	return
}
